using UnityEngine;
using System.Collections.Generic;

public class TrackFactory : MonoBehaviour
{
	#region Properties
	//The constants governing track section size and smoothening are loaded dynamically.
	private static TrackConstants _constants;
	private static TrackConstants constants
	{
		get
		{
			if (_constants == null)
            {
                _constants = GameObject.FindObjectOfType(typeof(TrackConstants)) as TrackConstants;
            }
            return _constants;
		}
	}
	
	//The constants are extracted from TrackConstants
	public static int railWidth
	{
		get
		{
			return constants.railWidth;
		}
	}
	
	public static int railOffset
	{
		get
		{
			return constants.railOffset;
		}
	}
	
	public static int maxTrackSectionLength
	{
		get
		{
			return constants.maxTrackSectionLength;
		}
	}
	
	public static int maxTrackVertexLength
	{
		get
		{
			return constants.maxTrackVertexLength;
		}
	}
	
	//The rail and stopper prefabs is located when needed and made availible to all static methods.
	private static Transform _rail;
	public static Transform rail
	{
		get
		{	//Fetch the rail object if it hasn't been fetchd yet
			if (_rail == null)
            {
                _rail = GameObject.FindWithTag("Rail Section").GetComponent<Transform>();
            }
            return _rail;
		}
	}
	
	private static Transform _stopper;
	public static Transform stopper
	{
		get
		{	//Fetch the stopper object if it hasn't been fetchd yet
			if (_stopper == null)
            {
                _stopper = GameObject.FindWithTag("Stopper Section").GetComponent<Transform>();
            }
            return _stopper;
		}
	}
	
	private static Transform _terminal;
	public static Transform terminal
	{
		get
		{	//Fetch the stopper object if it hasn't been fetchd yet
			if (_terminal == null)
            {
                _terminal = GameObject.FindWithTag("Terminal").GetComponent<Transform>();
            }
            return _terminal;
		}
	}
	#endregion
	
	/*Generates a vector of Transforms, each of them being a track section.
	* Together, they form a track passing through each SplineControlPoint
	*/
	public static Spline3D[] genTrack(SplineControlPoint[] points, Transform type)
	{
		//First, subdivide the track into a set of track sections
		Spline3D[] paths = (new Spline3D(points)).Rasterize(maxTrackSectionLength);
		
		//Set the default type of the track
		foreach(Spline3D path in paths)
		{
			path.type = TrackFactory.rail;
		}
		
		//return the path for later use
		return paths;
	}
	
	public static Transform[] genRails(Spline3D[] paths)
	{
		List<Transform> tracks = new List<Transform>();
		foreach(Spline3D path in paths)
		{
			if(path.type != TrackFactory.terminal)
			{
				path.Smooth(2);
			}
			tracks.Add(genRail(path, railWidth, railOffset, path.type, path.layer));
		}
		
		return tracks.ToArray();
	}

	/*This generates the mesh for the rail and sets up the rail script properties
	 * for use by the track manager
	 */
	private static Transform genRail(Spline3D path, float railWidth, int offsetFactor, Transform type, int layer)
	{
		Transform newRail = MonoBehaviour.Instantiate(type) as Transform;
		
		//A railway has two rails
		Mesh leftMesh = genMesh (path, railWidth, offsetFactor);
		Mesh rightMesh = genMesh (path, railWidth, -offsetFactor);
		
		newRail.Find("LeftRail").GetComponent<MeshFilter>().mesh = leftMesh;
		newRail.Find("LeftRail").GetComponent<MeshCollider>().sharedMesh = leftMesh;
		newRail.Find("LeftRail").gameObject.layer = layer;
		
		newRail.Find("RightRail").GetComponent<MeshFilter>().mesh = rightMesh;
		newRail.Find("RightRail").GetComponent<MeshCollider>().sharedMesh = rightMesh;
		newRail.Find("RightRail").gameObject.layer = layer;
		
		//Constants for the track manager.
		newRail.GetComponent<Track>().coord = path.center;
		newRail.GetComponent<Track>().rot = path.direction;
		
		newRail.GetComponent<Track>().startControlPoint = path.startSplineControlPoint;
		newRail.GetComponent<Track>().endControlPoint = path.endSplineControlPoint;
		
		return newRail;
	}
	
	/* This is where the magic happens
	 */
	private static Mesh genMesh(Spline3D path, float railWidth, int offsetFactor)
	{
		Mesh mesh = new Mesh();
		
		//Generating a mesh programatically requires a list of vertices and a list of integers
		// detailing what vertices form a triangle
		List<Vector3> verts = new List<Vector3>();
		List<int> triangles = new List<int>();
		
		/*From the points on the path, we deduce the foreward and sideways direction at that point
		* in the path. It's currently assumed that the up-direction for the path is the up-direction
		* of the world. This appears to cause inversions if the path goes upside-down.
		* Further testing is required to find out of it can happen in a realistic scenario or not
		*/
		Vector3 direction;
		Vector3 left;
		
		//We keep track of how many vertices have been processed, for the triangle array.
		int i = 0;
		foreach(Vector3[] pointPair in path)
		{
			//The foreward direction is taken to be from the start point to the
			// end point of the section under consideration. From this, the
			// local leftward direction is also deduced
			direction = pointPair[1] - pointPair[0];
			left = railWidth * Vector3.Cross(Vector3.up, direction).normalized;
			
			//If this is the first section of the track, we need to set up the
			// front face.
			if(verts.Count == 0)
			{
				verts.Add(pointPair[0] + left + Vector3.up + offsetFactor*left);
				verts.Add(pointPair[0] + left - Vector3.up + offsetFactor*left);
				verts.Add(pointPair[0] - left + Vector3.up + offsetFactor*left);
				verts.Add(pointPair[0] - left - Vector3.up + offsetFactor*left);
			}
			
			//For each section, we then add the vertices of the back face.
			verts.Add(pointPair[1] + left + Vector3.up + offsetFactor*left);
			verts.Add(pointPair[1] + left - Vector3.up + offsetFactor*left);
			verts.Add(pointPair[1] - left + Vector3.up + offsetFactor*left);
			verts.Add(pointPair[1] - left - Vector3.up + offsetFactor*left);
			
			//And we set up the triangles for the track. Note that we only generate
			// triangles for the top, left and right side. The bottom of the track and
			// the front and back end don't have faces.
			//Top side
			triangles.AddRange(new int[] {i+4, i, i+2});
			triangles.AddRange(new int[] {i+4, i+2, i+6});
			
			//Left side
			triangles.AddRange(new int[] {i+6, i+2, i+3});
			triangles.AddRange(new int[] {i+6, i+3, i+7});
			
			//Right side
			triangles.AddRange(new int[] {i, i+4, i+1});
			triangles.AddRange(new int[] {i+1, i+4, i+5});
			
			//Finally, we increment the counter holding the vertices processed by 4, since
			// we process 4 vertices at a time.
			i += 4;
		}
		
		Vector3[] vertices = verts.ToArray();
		
		Vector2[] uvs = new Vector2[vertices.Length];
        for(int j = 0; i < uvs.Length; i++) {
            uvs[j] = new Vector2(vertices[j].x, vertices[j].z);
            i++;
        }
		
		//Finally, we add the vertices and faces to the mesh
		mesh.vertices = vertices;
		mesh.triangles = triangles.ToArray();
		mesh.uv = uvs;
		
		//Calculate the normals for propper physics behavior
		mesh.RecalculateNormals();
		
		//And return the mesh.
		return mesh;
	}
}

